package io.kiki.sba.registry.net;


import io.kiki.sba.registry.util.StringFormatter;
import io.kiki.sba.registry.util.SystemUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


public class NetUtil {


    public static final String LOCALHOST = "127.0.0.1";

    public static final String ANYHOST = "0.0.0.0";
    /**
     * symbol :
     */
    public static final char COLON = ':';
    public static final List<String> NETWORK_INTERFACE_PREFERENCE = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(new String[]{"eth0", "en0"})));
    public static final String NETWORK_INTERFACE_BINDING = "network_interface_binding";
    public static final String NETWORK_INTERFACE_BINDING_VALUE = SystemUtils.getSystem(NETWORK_INTERFACE_BINDING);
    public static final String NETWORK_INTERFACE_DENYLIST = "network_interface_denylist";
    public static final List<String> NETWORK_INTERFACE_DENYLIST_VALUE = SystemUtils.getSystem(NETWORK_INTERFACE_DENYLIST) == null ? Collections.emptyList() : Arrays.asList(StringUtils.trim(System.getProperty(NETWORK_INTERFACE_DENYLIST)).split(","));
    private static final Logger logger = LoggerFactory.getLogger(NetUtil.class);
    private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
    private static volatile InetAddress LOCAL_ADDRESS = null;


    public static String genHost(String ip, int port) {
        return ip + COLON + port;
    }

    public static InetSocketAddress getLocalSocketAddress() {
        InetAddress address = getLocalAddress();
        String addressStr = address == null ? LOCALHOST : address.getHostAddress();
        return new InetSocketAddress(addressStr, 0);
    }


    public static String getIPAddressFromDomain(String domain) {
        try {
            InetAddress a = InetAddress.getByName(domain);
            if (LOCALHOST.equalsIgnoreCase(a.getHostAddress())) {
                a = getLocalAddress();
            }
            return a.getHostAddress();
        } catch (UnknownHostException e) {
            throw new RuntimeException("Unknown host {" + domain + "}", e);
        }
    }


    public static String getDomainFromIP(String ip) {
        try {
            InetAddress a = InetAddress.getByName(ip);
            return a.getCanonicalHostName();
        } catch (UnknownHostException e) {
            logger.error("[NetWorkUtils] Can not resolve ip " + ip + ". error is .", e);
        }
        return null;
    }

    /**
     * Gets local address.
     *
     * @return loccal IP all network card
     */
    public static InetAddress getLocalAddress() {
        if (LOCAL_ADDRESS != null) {
            return LOCAL_ADDRESS;
        }
        InetAddress localAddress = getLocalAddress0();
        LOCAL_ADDRESS = localAddress;
        return localAddress;
    }

    /**
     * To address string string.
     *
     * @param address the address
     * @return the string
     */
    public static String toAddressString(InetSocketAddress address) {
        if (address == null || address.getAddress() == null) {
            logger.error("InetSocketAddress to Address String error!In put inetSocketAddress or InetSocketAddress.getAddress is null");
            throw new RuntimeException("InetSocketAddress to Address String error!In put inetSocketAddress or InetSocketAddress.getAddress is null!");
        }
        return address.getAddress().getHostAddress() + COLON + address.getPort();
    }

    private static boolean isValidAddress(InetAddress address) {
        if (address == null || address.isLoopbackAddress()) {
            return false;
        }
        String name = address.getHostAddress();
        return (name != null && !ANYHOST.equals(name) && !LOCALHOST.equals(name) && IP_PATTERN.matcher(name).matches());
    }

    static InetAddress getLocalAddress0() {
        try {
            List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
            if (CollectionUtils.isEmpty(interfaces)) {
                throw new RuntimeException("no network interfaces");
            }
            if (StringUtils.isNotBlank(NETWORK_INTERFACE_BINDING_VALUE)) {
                InetAddress localAddress = selectLocalAddress(interfaces, Collections.singletonList(NETWORK_INTERFACE_BINDING_VALUE));
                if (localAddress != null) {
                    return localAddress;
                }
                throw new RuntimeException(StringFormatter.format("cannot found network interface {}", NETWORK_INTERFACE_BINDING_VALUE));
            }
            if (!CollectionUtils.isEmpty(NETWORK_INTERFACE_DENYLIST_VALUE)) {
                interfaces = interfaces.stream().filter(i -> !NETWORK_INTERFACE_DENYLIST_VALUE.contains(i.getName()) && !NETWORK_INTERFACE_DENYLIST_VALUE.contains(i.getDisplayName())).collect(Collectors.toList());
            }
            if (!CollectionUtils.isEmpty(NETWORK_INTERFACE_PREFERENCE)) {
                InetAddress localAddress = selectLocalAddress(interfaces, NETWORK_INTERFACE_PREFERENCE);
                if (localAddress != null) {
                    return localAddress;
                }
            }
            for (NetworkInterface ni : interfaces) {
                List<InetAddress> addresses = Collections.list(ni.getInetAddresses());
                for (InetAddress address : addresses) {
                    if (isValidAddress(address)) {
                        return address;
                    }
                }
            }
            throw new RuntimeException("no valid network address");
        } catch (Throwable e) {
            logger.error("Failed to retriving ip address: ", e);
            throw new RuntimeException(StringFormatter.format("Failed to retriving ip address: {}", e.getMessage()));
        }
    }

    static InetAddress selectLocalAddress(List<NetworkInterface> nis, List<String> bindings) {
        for (NetworkInterface ni : nis) {
            if (!bindings.contains(ni.getDisplayName()) && !bindings.contains(ni.getName())) {
                continue;
            }
            List<InetAddress> addresses = Collections.list(ni.getInetAddresses());
            for (InetAddress address : addresses) {
                if (isValidAddress(address)) {
                    return address;
                }
            }
        }
        return null;
    }
}
